#include "HeapSort.h"
#include "../utils/FileSystem.h"
#include <iostream>



HeapSort::HeapSort(std::string nomArchDesordenado, unsigned int cantElem) :
	_heap(10 * cantElem),
	_buffEntrada(cantElem), _buffSalida(cantElem), _buffCongelamiento(cantElem)
{
	// Abre el archivo des-ordenado
	this->_archDesordenado.open(nomArchDesordenado.c_str(), std::fstream::in | std::fstream::binary);
	if (!this->_archDesordenado) throw std::runtime_error("No se pudo abrir el archivo de ocurrencia de terminos");
}


HeapSort::~HeapSort() {
	if (this->_archDesordenado.is_open()) this->_archDesordenado.close();
}

//------------------------------------------------

// Ordena el archivo des-ordenado y guarda el resultado en otro archivo distinto
void HeapSort::sort(){

	// Llevamos la cuenta de cuantas particiones creamos para el merge
	int nroParticion = 0;
	while (!this->_heap.estaVacio()){

		// Ordena hasta que se llene el buffer de salida, el congelador o se vacie el buffer de entrada o el heap
		Resultado resul = ordenarParticion(this->_archDesordenado.eof());

		// Vacia el buffer de salida creando una particion
		if (resul & SALIDA_LLENA){
			crearParticion(this->_buffSalida, ++nroParticion);
		}

		// Llena el buffer de entrada desde el archivo des-ordenado
		if ( (resul & ENTRADA_VACIA) && !this->_archDesordenado.eof()){
			cargarBuffer(this->_buffEntrada);
		}

		// Vacia el congelador creando una particion
		if (resul & CONGELADOR_LLENO){
			descongelarBuffer(this->_buffCongelamiento, ++nroParticion);
		}

		// Llena el heap usando el contenido del buffer de entrada
		if ( (resul & HEAP_VACIO) && this->_buffEntrada.getCantElem() > 0){
			cargarHeap(this->_buffEntrada, this->_heap);
		}
	}

	// Si el congelador o el buffer de salida no estan vacios hay que crear nuevas particiones
	if (this->_buffCongelamiento.getCantElem() >0){
		descongelarBuffer(this->_buffCongelamiento, ++nroParticion);
	}

	if (this->_buffSalida.getCantElem() >0){
		crearParticion(this->_buffSalida, ++nroParticion);
	}

	// En este punto ya no necesitamos el archivo de entrada des-ordenado
	this->_archDesordenado.close();

	// Señala cuentas particiones existen y de cuantas hay que procesar en simultaneo
	int cantFusiones = (nroParticion /4 < 2) ? 2 : nroParticion / 4;
	this->fusorParticiones.fusionarParticiones(nroParticion, cantFusiones);
}


// Ordena hasta que se llene el buffer de salida, el congelador o se vacie el buffer de entrada o el heap
HeapSort::Resultado HeapSort::ordenarParticion(bool archivoVacio){
	Resultado estado = OK;
	do {
		// Saca el primer termino del heap y lo guarda en el buffer de salida
		RegistroOcurrencia reg;
		bool exito = this->_heap.remover(reg);
		if (exito) this->_buffSalida.agregarElem(reg);

		// Si todavia hay elementos en el buffer de entrada puedo hacer "natural selection"
		naturalSelection(reg);

		// Chequea el estado de los buffers y del heap
		estado = evaluarBuffers(archivoVacio);

	} while (estado == OK);

	return estado;
}


// Si es posible realiza una seleccion natural de los elementos en los buffer y en el heap
bool HeapSort::naturalSelection(RegistroOcurrencia reg){
	// Solo se puede hacer seleccion natural si hay elementos en el buffer de entrada
	if (this->_buffEntrada.getCantElem() == 0) return false;

	RegistroOcurrencia regGuardado = reg;
	this->_buffEntrada.quitarElemento(reg);

	// Si el elemento leido es mayor que el extraido
	if (reg.getTermino() > regGuardado.getTermino()){
		// Se agrega al heap
		this->_heap.insertar(reg);
		return true;

	} else {
		// Sino, se congela
		this->_buffCongelamiento.agregarElem(reg);
		return false;
	}
}


// Señala el estado en que se encuentran los buffers
HeapSort::Resultado HeapSort::evaluarBuffers(bool archivoVacio) const {

	// Si el buffer de entrada esta vacio pero todavia quedan elementos en el archivo puedo volver a llenarlo
	Resultado R1 = OK;
	if (this->_buffEntrada.getCantElem() == 0 && !archivoVacio) R1 = ENTRADA_VACIA;

	// Si el buffer de salida esta lleno hay que vaciarlo creando una nueva partición
	Resultado R2 = OK;
	if (this->_buffSalida.getCantElem() == this->_buffSalida.getCantMax()) R2 = SALIDA_LLENA;

	// Si el buffer de congelamiento esta lleno hay que vaciarlo creando una nueva partición
	Resultado R3 = OK;
	if (this->_buffCongelamiento.getCantElem() == this->_buffCongelamiento.getCantMax()) R3 = CONGELADOR_LLENO;

	// El heap esta vacio
	Resultado R4 = OK;
	if (this->_heap.estaVacio()) R4 = HEAP_VACIO;

	// No me gustó como quedó, pero parece que funciona...
	return static_cast<Resultado>(R1 | R2 | R3 | R4);
}

//------------------------------------------------

// Dado un buffer lo guarda como una particion
void HeapSort::crearParticion(Buffer<RegistroOcurrencia>& buff, int nroParticion){

	// Guarda las particiones en el directorio correspondiente
	std::string nomArch = "particiones/" + FileSystem::getNombreParticion(nroParticion);
	std::fstream fout(nomArch.c_str(), std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);

	while (buff.getCantElem() != 0){
		RegistroOcurrencia reg;
		buff.quitarElemento(reg);

		// Por alguna razon el primer el elemento es un termino vacio, se saca otro
		if (reg.getTermino() == "") buff.quitarElemento(reg);

		fout << reg;
	}

	fout.close();
}


// Ordena todo el buffer y lo guarda en un particion
void HeapSort::descongelarBuffer(Buffer<RegistroOcurrencia>& buff, int nroParticion){

	// Un heap temporal del mismo tamaño que el buffer
	Heap<RegistroOcurrencia> hTemp(buff.getCantElem());

	// Carga el heap y vacia el buffer
	cargarHeap(buff, hTemp);

	// Mueve los elementos ordenados al buffer
	while (!hTemp.estaVacio()){
		RegistroOcurrencia ro;
		hTemp.remover(ro);
		buff.agregarElem(ro);
	}

	// Guarda todo el buffer ordenado en su propia particion
	crearParticion(buff, nroParticion);
}

//------------------------------------------------

bool HeapSort::init(std::string nomArchFinal){
	// Crea la carpeta donde se guardaron las particiones
	FileSystem::createFolder("particiones");

	// Inicializa el encargado de la fusion de las particiones
	this->fusorParticiones.init("particiones", nomArchFinal);

	// Inicializar el buffer de entrada y el heap
	bool exito = initHeap();
	if (!exito) return false;

	exito = cargarBuffer(this->_buffEntrada);
	if (!exito) return false;

	// El heap y el buffer de entrada estan llenos
	return true;
}

//------------------------------------------------

// Llena el heap con los datos del archivo
bool HeapSort::initHeap(){

	while (!this->_archDesordenado.eof() && !this->_heap.estaLleno()){

		RegistroOcurrencia reg;
		this->_archDesordenado >> reg;

		// Con cada inserción se hace un ordenamiento
		if (!this->_archDesordenado.eof()) this->_heap.insertar(reg);
	}

	// Si el heap esta lleno todo esta bien
	return this->_heap.estaLleno();
}


// Carga el contenido del buffer en el heap
void HeapSort::cargarHeap(Buffer<RegistroOcurrencia>& buff, Heap<RegistroOcurrencia>& heap){
	while (!heap.estaLleno() && buff.getCantElem() != 0){
		RegistroOcurrencia ro;
		buff.quitarElemento(ro);
		heap.insertar(ro);
	}
}


// Llena el buffer con datos del archivo des-ordenado
bool HeapSort::cargarBuffer(Buffer<RegistroOcurrencia>& buff){

	while (!this->_archDesordenado.eof() && buff.getCantElem() < buff.getCantMax()){

		RegistroOcurrencia reg;
		this->_archDesordenado >> reg;

		buff.agregarElem(reg);
	}

	// Si esta lleno vamos bien
	if (buff.getCantElem() < buff.getCantMax()){
		return false;

	} else {
		return true;
	}
}




